#ifndef JOB_HEADER_FLAG
#define JOB_HEADER_FLAG
// The included directories
#include "option.h"
//#include "getvoltages.h"
#include <stdio.h> 		// Needed before NIDAQmx.h with MinGW
#include "NIDAQmx.h"
#include "mathlink.h"
#include <math.h>
#include <string>
#include <algorithm>
#include <sstream>
#include <map>
#include <vector>
#include<string>
#include "trigger_state.h"
using namespace std;			// bad idea! put in both cpp files? use using declaration in cpp files

class persistJobHolder;			// this declaration needed for Job Class declaration

//class arrayHolder
//{
//public:
//    arrayHolder(long groupingMethod, long samplesPerChannel, int numberOfChannels);
//    arrayHolder(long size, int numberOfChannels = 1);  //old construct remove and use preceding ctor
//    ~arrayHolder();
//    long    getSize() {return size;}
//    double* getData() {return data;}
//    int     getNumChans() {return numChans;}
//    bool32  getGroupMethod() {return groupMethod;}
//    long    getSampsPerChan() {return samplesPerChan;}
//    double getSample(int channel, long index) {return channel + numChans * index;}  // remove
//
//private:
//    bool32    groupMethod;
//    double* data;
//    long    size;           //remove or rename
//    long    samplesPerChan;
//    int     numChans;
//};

//namespace AddressCalculatorHelper
//{
//    inline double* addrByScan(double* offset, int numChannels, long index){return offset + numChannels*index;}
//    inline double* addrByChannel(double* offset, int numChannels, long index){return offset + index;}
//};
//
//class AddressCalculator
//{
//public:
//    AddressCalculator(arrayHolder& ah, int channel);
//    inline double* operator()(long index){return addr(offset, numChans, index);}
//private:
//    double* offset;
//    int numChans;
//    double* (*addr)(double*, int, long);
//};
//
//class ChanPointer
//{
//public:
//    ChanPointer(arrayHolder& ah, int channelNumber) :
//        addrCalc(ah, channelNumber), pointerIndex(0) {}
//    ~ChanPointer() {}
//    double& operator[](long index) {return *addrCalc(pointerIndex + index);}
//    ChanPointer operator++(){++pointerIndex; return *this;}
//    ChanPointer operator++(int){ChanPointer temp = *this; pointerIndex++; return temp;}
//    double& operator*(){return *addrCalc(pointerIndex);}
//
//private:
//    AddressCalculator addrCalc;
//    long pointerIndex;
//};

class Job
{
public:
					Job(string taskname, persistJobHolder&);
					Job(string taskname, persistJobHolder&, option&);
					Job(Job&);
	virtual			~Job();
	virtual Job*	clone()=0;				// returns pointer like new does

	option&			getOptions(){return opt;}
	long			getoptL(string name) {return opt.getL(name);}
	double			getoptD(string name) {return opt.getD(name);}
	string			getoptS(string name) {return opt.getS(name);}
	double*			getoptDArray(string name) {return opt.getDArray(name);}

	TaskHandle		getTaskHandle() {return taskHandle;} // constant reference?
	void			clearTaskHandle(){taskHandle = 0;}		// NOT clearTask
	int				startTask(TaskHandle taskHandle);
	void virtual	continueTask(double arg1 = 0.0, double arg2 = 0.0);
	void virtual	configureClock(long bufferSizeInSamples, double numPeriods = 1.0);
	bool virtual	sendToMathematica()=0;
	int32 iErr;			// derived class must be able to change
private:
	TaskHandle			taskHandle;
	option				opt;
};
// defined in file scope in job.cpp
// not in header? int Job::numTasks =0;

class InputJob : public Job
{
	// at least for now lets use inputjob for single input
public:
//						InputJob(string taskname = "Default Job");
						InputJob(string taskname, persistJobHolder&);
						InputJob(string taskname, persistJobHolder&, option&);

						InputJob(InputJob&);
	virtual				~InputJob();
	virtual	InputJob*	clone() {return new InputJob(*this);}
	void virtual		continueTask(double arg1 = 0.0, double arg2 = 0.0);
	bool virtual		isDoneNoError(){return true;}
	int	virtual			readData();
	bool virtual		sendToMathematica();

    double*             getPostTrigData(){return pData->getData();}
    long				getPostTrigSize(){return pData->getSize();}
	void				setTransferMech();
private:
    void                doConstruction();
	void		        createAIChan();
	arrayHolder*        pData;
};

class BufferedInputJob : public InputJob
{
public:
						BufferedInputJob(string taskname, persistJobHolder&);
						BufferedInputJob(string taskname, persistJobHolder&, option&);
						BufferedInputJob(BufferedInputJob& old);  // declared but not implemented to prevent automatic generation by compiler
	virtual				~BufferedInputJob();
	void virtual		continueTask(double arg1 = 0.0, double arg2 = 0.0);
	bool virtual		isDoneNoError();
//	void				setTransferMech();

private:
    void        doConstruction();
};

class TrigJob : public InputJob
/// Virtual class for SoftTrigJob and DigitalTrigJob
{
public:
                        TrigJob(string taskname, persistJobHolder&);
						TrigJob(string taskname, persistJobHolder&, option&);
						TrigJob(TrigJob& old);
    virtual             ~TrigJob();

};

class SoftTrigJob : public TrigJob
{
public:
						SoftTrigJob(string taskname, persistJobHolder&);
						SoftTrigJob(string taskname, persistJobHolder&, option&);
//						SoftTrigJob(SoftTrigJob& old);
	virtual				~SoftTrigJob();
	bool virtual		sendToMathematica();
	long				waitForTrigger();
	void				readPreTrigger();
	void				readPostTrigger();
	long                getTestingArraySize() {return testData->getSize();}
	double*             getTestingArray() {return testData->getData();}
    long                getPreTrigArraySize() {return preData->getSize();}
	double*             getPreTrigArray() {return preData->getData();}
    double*             getPostTrigData(){return postData->getData();}
    long				getPostTrigSize(){return postData->getSize();}
    ChanPointer         getChanPointerTestData(int channel) {return ChanPointer(*testData, channel);}

//	bool virtual		isDoneNoError();
private:
	void				doConstruction();
	long				readPastTrig;
	arrayHolder         *preData, *postData, *testData;
};

class DigitalTrigJob : public TrigJob
{
public:
						DigitalTrigJob(string taskname, persistJobHolder&);
						DigitalTrigJob(string taskname, persistJobHolder&, option&);
						DigitalTrigJob(DigitalTrigJob& old);
	virtual				~DigitalTrigJob();
//	bool virtual		sendToMathematica();
//	bool virtual		isDoneNoError();
    int                 readData();
    bool                sendToMathematica();

    double*             getPostTrigData();
    long				getPostTrigSize();
    double*             getTestData(){return testData->getData();}
    long				getTestSize(){return testData->getSize();}
private:
    void				doConstruction();
    arrayHolder         *testData;
};

class OutputJob : public Job
{
public:
						OutputJob(string taskname, persistJobHolder&);
						OutputJob(string taskname, persistJobHolder&, option&);
						OutputJob(OutputJob&);
	virtual		    	~OutputJob();
	virtual		    	OutputJob*	clone(){return new OutputJob(*this);}
	bool virtual	    sendToMathematica();
	void virtual	    continueTask(double ch0Volt = 0.0, double ch1Volt = 0.0);
	void virtual	    createAOChan();
    void virtual		setTransferMech();
	void				writeData();
	float64		    	dataStorage[2];
private:
	void				doConstruction();

};
class BufferedOutputJob : public OutputJob
{
public:
						BufferedOutputJob(string taskname, persistJobHolder&);
						BufferedOutputJob(string taskname, persistJobHolder&, option&);
						BufferedOutputJob(BufferedOutputJob&);
	virtual				~BufferedOutputJob();
						virtual BufferedOutputJob*	clone(){return new BufferedOutputJob(*this);}
//	void virtual		setTransferMech();
	void virtual		waveformToBuffer();
	void virtual		continueTask(double ch0Volt = 0.0, double ch1Volt = 0.0);
private:
	void				doConstruction();
};

struct JobState
	// If JobState is zero then when task is done it is cleared
	// if not zero task persist to be run again;
{
	Job*	pJob;
	long	runAgainCount;				// 0 = runOnce, 1 = runContinuous
};										// runRepeated =0 --> finite task,
										// quit if task is done
class persistJobHolder
/*	Inteded to be used as a global class
	When changing a job to a persistant job set taskHandle to 0 on old job
	 so that destructor can be called on old job without clearing the task
	 need copy constructor for each class since we won't in general now the details
	 all persistant jobs created with new, so ok to call delete
	 copy constructor including call to base class copy constructor
	 persistJobHolder needs to know message level
*/
{
public:
	~persistJobHolder();
	Job&		changeToPersist(Job &j, long runAgainCount);		// wiil know own TaskHandle
	Job&		getJob(TaskHandle task){return *(jobList[task].pJob);}  // lets try retruning reference for ease of use?
	void		runJob(TaskHandle task);
	void		clearFinishedJobs();
	void		clearJob(TaskHandle task);
	map<TaskHandle,JobState>		getJobList() { return jobList;}
private:
	map<TaskHandle, JobState> jobList;
};
class TrigTest
{
//* modifies triggered and softwareTrig::readpast trigger
// should this be a method in SoftTrig job?
// note how I am using reference to modify private data member
public:
    TrigTest(SoftTrigJob& stj, long& rpt) :    data(stj.getTestingArray()),
                                    trigLevel(stj.getoptD("triggerlevel")),
                                    numChannels(stj.getoptL("numchannels")),
                                    triggerenabled(false),
                                    samplesAvailable(0),
                                    preTrigSamples(stj.getoptL("pretrigsamples")),
                                    readPastTrig(rpt)
                                    {
                                        //sendmessage("triggerslope option is ", stj.getoptL("triggerslope"));
                                        if (stj.getoptL("triggerslope") == DAQmx_Val_FallingSlope)
                                            trigSlope = -1.0;
                                        else
                                            trigSlope = 1.0;
                                    }

   inline bool IsTriggered(int32 samplesInBuffer)
    {
        for(int i=0, j=0; i < samplesInBuffer; i++, j += numChannels)
        {
            if (trigSlope * (data[j] - trigLevel) < 0)
                triggerenabled = true;
            if (triggerenabled && (trigSlope * (data[j] - trigLevel) > 0)
                    && samplesAvailable + i > preTrigSamples)
            {
                readPastTrig = samplesInBuffer - i;
                return true;
            }
        }
        samplesAvailable += samplesInBuffer;
        return false;
    }

private:
    double* data;
    double trigLevel;
    double trigSlope;
    long numChannels;
    long& readPastTrig;
    bool triggerenabled;
    long samplesAvailable;
    long preTrigSamples;
};


#endif


